# -*- shell-script -*-

set -eu
[ x"${GMB_SHELL_DEBUG:-no}" = x"no" ] || set -x


GMB_CMD_CHECK_RUN=0
GMB_CMD_CHECK_SKIP=1


GMB_COMMANDS_LIST="
checkout
update
sync
bootstrap
configure
build
install

run
wipe
"


# gmb_command_select
#
gmb_command_select()
{
    local short=$(echo "${1}" | tr '-' '_')
    local sel=

    for cmd in ${GMB_COMMANDS_LIST}; do
        # might give false positive if len(short) > len(cmd)
        if [ ${#short} -le ${#cmd} ]; then
            local cmdpfx=$(echo "${cmd}" | cut -b-${#short})
            if [ x"${cmdpfx}" = x"${short}" ]; then
                [ x"${sel}" = x ] || gmb_error "ambiguous command: '${short}' (matches ${sel} and ${cmd})"
                sel="${cmd}"
            fi
        fi
    done
    [ x"${sel}" != x ] || gmb_error "unknown command: ${short}"
    echo "${sel}"
}


# gmb_command_object_type COMMAND
#
gmb_command_object_type()
{
    case "${1}" in
        checkout|update|bootstrap)
            echo "repository"
            ;;
        sync|configure|build|install)
            echo "build"
            ;;
        wipe|run)
            echo "target"
            ;;
        *)
            gmb_error "gmb_command_get_object_type: TODO: ${1}"
    esac
}


# gmb_command_stampname COMMAND [-t TARGET] [-o OBJECT]
#
gmb_command_stampname()
{
    local command=
    local target=
    local object=
    while [ ${#} -gt 0 ]; do
        case "${1}" in
            -t) target="${2}"; shift 2;;
            -o) object="${2}"; shift 2;;
            *)
                [ x"${command}" = x ] || gmb_error "gmb_command_stampname: multiple commands"
                command="${1}"
                shift
        esac
    done
    [ x"${command}" != x ] || gmb_error "gmb_command_stampname: command missing"
    local objtype=$(gmb_command_object_type "${command}")
    case "${objtype}" in
        repository)
            echo "repositories/${object:-${GMB_REPOSITORY_NAME}}.${command}"
            ;;
        build)
            echo "builds/${target:-${GMB_TARGET_NAME}}/${object:-${GMB_BUILD_NAME}}.${command}"
            ;;
        target)
            echo "targets/${target:-${GMB_TARGET_NAME}}.${command}"
            ;;
        *)
            gmb_error "gmb_command_stampname: TODO: objtype ${objtype}"
    esac
}


# gmb_command_stampfile
#
gmb_command_stampfile()
{
    local stampname=$(gmb_command_stampname ${@})
    echo "${GMB_STAMPS_DIR}/${stampname}.stamp-h"
}


# gmb_command_mtime
#
gmb_command_mtime()
{
    local stampfile=$(gmb_command_stampfile ${@})
    if [ -f "${stampfile}" ]; then
        stat -c '+%Y' "${stampfile}"
    else
        echo '0'
    fi
}


# gmb_command_touch
#
gmb_command_touch()
{
    local stampfile=$(gmb_command_stampfile ${@})
    gmb_debug "touch stamp: '${stampfile}'"
    local stampdir=$(dirname "${stampfile}")
    [ -d "${stampdir}" ] || mkdir -vp "${stampdir}"
    echo >"${stampfile}"
}


######################################################################
#                              MANAGER                               #
######################################################################


# gmb_manager_process_order -c COMMANDS -t TARGETS -o OBJECTS
#
gmb_manager_process_order()
{
    local commands=
    local targets=
    local objects=
    while [ ${#} -gt 0 ]; do
        case "${1}" in
            -c) commands="${2}"; shift 2;;
            -t) targets="${2}"; shift 2;;
            -o) objects="${2}"; shift 2;;
            *) gmb_error "gmb_manager_process: invalid option: '${1}'"
        esac
    done

    for cmd in ${commands:-install}; do
        cmd=$(gmb_command_select "${cmd}")
        local objtype=$(gmb_command_object_type "${cmd}")
        case "${objtype}" in
            repository)
                for repo in ${objects}; do
                    gmb_env_push
                    gmb_config_repository "${repo}"
                    gmb_manager_process "${cmd}"
                    gmb_env_pop
                done
                ;;
            build)
                for targ in ${targets:-${GMB_DEFAULT_TARGET}}; do
                    gmb_env_push
                    gmb_config_target "${targ}"
                    for build in ${objects}; do
                        gmb_env_push
                        gmb_config_build "${build}"
                        gmb_manager_process "${cmd}"
                        gmb_env_pop
                    done
                    gmb_env_pop
                done
                ;;
            target)
                for targ in ${targets:-${GMB_DEFAULT_TARGET}}; do
                    gmb_env_push
                    gmb_config_target "${targ}"
                    # [fixme] should be set globaly for all commands?
                    # (like GMB_SELECTED_OBJECTS)
                    export GMB_TARGET_COMMAND_BUILDS="${objects}"
                    gmb_manager_process "${cmd}"
                    gmb_env_pop
                done
                ;;
            *)
                gmb_error "gmb_manager_process_order: TODO: objtype ${objtype}"
        esac
    done
}


# gmb_manager_process
#
gmb_manager_process()
{
    local command="${1}"

    : ${GMB_RECURSION:=0}
    GMB_RECURSION=$((GMB_RECURSION_LEVEL+1))
    
    gmb_manager_process_pre_run "${command}"
    if gmb_manager_process_check_run "${command}"; then
        gmb_manager_process_run "${command}"
    fi
}


# gmb_manager_process_pre_run
#
gmb_manager_process_pre_run()
{
    local command="${1}"
    eval "gmb_cmd_${command}_pre_run"
}


# gmb_manager_process_check_run
#
gmb_manager_process_check_run()
{
    local command="${1}"

    gmb_run_trace "${command}: check_run"

    local stampfile=$(gmb_command_stampfile "${1}") || exit 1

    if [ ${GMB_FORCE_ALL} -ne 0 ]; then
        gmb_run_trace "${command}: force-all enabled"
        return 0
    fi

    if [ ${GMB_FORCE} -ne 0 -a ${GMB_RECURSION} -le 1 ]; then
        gmb_run_trace "${command}: force enabled"
        return 0
    fi
    
    [ -f "${stampfile}" ] || {
        gmb_run_trace "${command}: stampfile does not exist (${stampfile})";
        return 0; }
    
    if eval "gmb_cmd_${command}_check_run"; then
        gmb_run_trace "${command}: command check_run returned 0"
        return 0
    else
        gmb_run_trace "${command}: up to date"
        return 1
    fi
}


# gmb_manager_process_run
#
gmb_manager_process_run()
{
    local command="${1}"
    eval "gmb_cmd_${command}_run"
    gmb_command_touch "${command}"
}


######################################################################
#                              DRIVERS                               #
######################################################################


# gmb_driver_run
#
gmb_driver_run()
{
    local command="${1}"
    local upcmd=$(echo "${command}" | gmb_strup)
    local objtype=$(gmb_command_object_type "${command}")
    local driver
    case "${objtype}" in
        repository)
            driver=$(eval "echo \${GMB_REPOSITORY_DRIVER_${upcmd}}")
            driver_path="${GMB_DRIVERS_DIR}/${driver}"
            gmb_debug "driver run: ${command} ${GMB_REPOSITORY_NAME} -> '${driver}' (${driver_path})"
            "${driver_path}" --run
            ;;
        build)
            driver=$(eval "echo \${GMB_BUILD_DRIVER_${upcmd}}")
            driver_path="${GMB_DRIVERS_DIR}/${driver}"
            gmb_debug "driver run: ${command} ${GMB_BUILD_NAME} -> '${driver}' (${driver_path})"
            "${driver_path}" --run
            ;;            
        *)
            gmb_error "gmb_driver_run: TODO: objtype ${objtype}"
    esac
}


######################################################################
#                              COMMANDS                              #
######################################################################


# gmb_cmd_checkout_pre_run
#
gmb_cmd_checkout_pre_run()
{
    :
}


# gmb_cmd_checkout_check_run
#
gmb_cmd_checkout_check_run()
{
    if [ x"${GMB_PACKAGE_URL}" = x ]; then
        gmb_run_trace "checkout: repository url not set, skipped"
        return ${GMB_CMD_CHECK_SKIP}
    fi
    
    if [ ! -d "${GMB_REPOSITORY_SOURCE_DIR}" ]; then
        gmb_run_trace "checkout: source dir does not exist: '${GMB_REPOSITORY_SOURCE_DIR}'"
        return ${GMB_CMD_CHECK_RUN}
    fi

    return ${GMB_CMD_CHECK_SKIP}
}


# gmb_cmd_checkout_run
#
gmb_cmd_checkout_run()
{
    if [ -d "${GMB_REPOSITORY_SOURCE_DIR}" ]; then
        gmb_info "NOTE: checkout ${GMB_REPOSITORY_NAME}: source dir already exists, skipped"
        return
    fi

    gmb_driver_run 'checkout'

    # touch update stamp to not run it immediately after checkout
    gmb_command_touch 'update'
}


# gmb_cmd_update_pre_run
#
gmb_cmd_update_pre_run()
{
    gmb_manager_process 'checkout'
}


# gmb_cmd_update_check_run
#
gmb_cmd_update_check_run()
{
    local mtime=$(gmb_command_mtime 'update')
    
    if [ x"${GMB_REPOSITORY_UPDATE}" != x"no" ]; then
        local now=$(date '+%s')
        local diff=$((now - mtime))
        if [ ${diff} -gt ${GMB_REPOSITORY_UPDATE_DELAY} ]; then
            gmb_run_trace "update: update delay expired (${diff} > ${GMB_REPOSITORY_UPDATE_DELAY})"
            return ${GMB_CMD_CHECK_RUN}
        fi
    fi
    
    return ${GMB_CMD_CHECK_SKIP}
}


# gmb_cmd_update_run
#
gmb_cmd_update_run()
{
    # NOTE: command may be run event if UPDATE=no, so we add some
    # extra checks
    if [ x"${GMB_REPOSITORY_UPDATE}" != x"yes" ]; then
        return
    fi
    
    gmb_driver_run 'update'
}


# gmb_cmd_bootstrap_pre_run
#
gmb_cmd_bootstrap_pre_run()
{
    gmb_manager_process 'update'
}


# gmb_cmd_bootstrap_check_run
#
gmb_cmd_bootstrap_check_run()
{
    return ${GMB_CMD_CHECK_SKIP}
}


# gmb_cmd_bootstrap_run
#
gmb_cmd_bootstrap_run()
{
    gmb_driver_run 'bootstrap'
}


# gmb_cmd_sync_pre_run
#
gmb_cmd_sync_pre_run()
{
    gmb_manager_process 'bootstrap'
}


# gmb_cmd_sync_check_run
#
gmb_cmd_sync_check_run()
{
    if [ x"${GMB_BUILD_IN_PLACE}" != x"no" ]; then
        if [ ! -d "${GMB_BUILD_SOURCE_DIR}" ]; then
            gmb_run_trace "sync: build/source dir does  not exist: ${GMB_BUILD_SOURCE_DIR}"
            return ${GMB_CMD_CHECK_RUN}
        fi
        local mtime=$(gmb_command_mtime 'sync')
        local update_mtime=$(gmb_command_mtime 'update')
        if [ ${update_mtime} -gt ${mtime} ]; then
            gmb_run_trace "sync: update stamp is newer"
            return ${GMB_CMD_CHECK_RUN}
        fi
    fi

    return ${GMB_CMD_CHECK_SKIP}
}


# gmb_cmd_sync_run
#
gmb_cmd_sync_run()
{
    [ x"${GMB_BUILD_IN_PLACE}" != x"no" ] || return 0
    
    gmb_info "SYNC: ${GMB_REPOSITORY_SOURCE_DIR} -> ${GMB_BUILD_SOURCE_DIR}"

    [ -d "${GMB_BUILD_SOURCE_DIR}" ] || mkdir -vp "${GMB_BUILD_SOURCE_DIR}"
    
    gmb_exec -c "${GMB_BUILD_SOURCE_DIR}" \
             rsync \
             --archive \
             --one-file-system \
             --delete \
             --delete-excluded \
             --exclude='.git' \
             "${GMB_REPOSITORY_SOURCE_DIR}/." \
             "${GMB_BUILD_SOURCE_DIR}" 
}


# gmb_cmd_configure_pre_run
#
gmb_cmd_configure_pre_run()
{
    for dep in ${GMB_PACKAGE_DEPENDS}; do
        gmb_env_push
        gmb_config_build "${dep}"
        gmb_manager_process 'install'
        gmb_env_pop
    done
    gmb_manager_process 'sync'
}


# gmb_cmd_configure_check_run
#
gmb_cmd_configure_check_run()
{
    local mtime=$(gmb_command_mtime 'configure')
    local boot_mtime=$(gmb_command_mtime 'bootstrap')
    if [ ${boot_mtime} -gt ${mtime} ]; then
        gmb_run_trace "configure: bootstrap stamp is newer"
        return ${GMB_CMD_CHECK_RUN}
    fi
    
    return ${GMB_CMD_CHECK_SKIP}
}


# gmb_cmd_configure_run
#
gmb_cmd_configure_run()
{
    [ -d "${GMB_BUILD_BUILD_DIR}" ] || mkdir -vp "${GMB_BUILD_BUILD_DIR}"
    gmb_driver_run 'configure'
}


# gmb_cmd_build_pre_run
#
gmb_cmd_build_pre_run()
{
    gmb_manager_process 'configure'
}


# gmb_cmd_build_check_run
#
gmb_cmd_build_check_run()
{
    local mtime=$(gmb_command_mtime 'build')
    local conf_mtime=$(gmb_command_mtime 'configure')
    if [ ${conf_mtime} -gt ${mtime} ]; then
        gmb_run_trace "build: configure stamp is newer"
        return ${GMB_CMD_CHECK_RUN}
    fi

    # check depends install
    for dep in ${GMB_PACKAGE_DEPENDS}; do
        local dep_mtime=$(gmb_command_mtime -o "${dep}" 'install')
        if [ ${dep_mtime} -gt ${mtime} ]; then
            gmb_run_trace "build: ${dep} install stamp is newer"
            return ${GMB_CMD_CHECK_RUN}
        fi
    done
    
    # check modified sources
    local stamp=$(gmb_command_stampfile 'build')
    local mfile=$(find "${GMB_BUILD_SOURCE_DIR}" -name '.git' -prune -o '(' -type f -newermm "${stamp}" -print -quit ')')
    if [ x"${mfile}" != x ]; then
        gmb_run_trace "build: source modified (${mfile})"
        return ${GMB_CMD_CHECK_RUN}
    fi

    return ${GMB_CMD_CHECK_SKIP}
}


# gmb_cmd_build_run
#
gmb_cmd_build_run()
{
    gmb_driver_run 'build'
}


# gmb_cmd_install_pre_run
#
gmb_cmd_install_pre_run()
{
    gmb_manager_process 'build'
}


# gmb_cmd_install_check_run
#
gmb_cmd_install_check_run()
{
    local mtime=$(gmb_command_mtime 'install')
    local build_mtime=$(gmb_command_mtime 'build')
    if [ ${build_mtime} -gt ${mtime} ]; then
        gmb_run_trace "install: build stamp is newer"
        return ${GMB_CMD_CHECK_RUN}
    fi

    return ${GMB_CMD_CHECK_SKIP}
}


# gmb_cmd_install_run
#
gmb_cmd_install_run()
{
    gmb_driver_run 'install'
}


######################################################################


# gmb_cmd_run_pre_run
#
gmb_cmd_run_pre_run()
{
    :
}


# gmb_cmd_run_check_run
#
gmb_cmd_run_check_run()
{
    return ${GMB_CMD_CHECK_RUN}
}


# gmb_cmd_run_run
#
gmb_cmd_run_run()
{
    [ x"${GMB_RUN_ARGV}" != x ] || GMB_RUN_ARGV='/bin/sh'

    gmb_info "setting env for target ${GMB_TARGET_NAME:-(none)}"
    gmb_env_exec
    exec ${GMB_RUN_ARGV}
}


######################################################################
#                           WIPE COMMANDS                            #
######################################################################


# gmb_cmd_wipe_pre_run
#
gmb_cmd_wipe_pre_run()
{
    :
}


# gmb_cmd_wipe_check_run
#
gmb_cmd_wipe_check_run()
{
    return ${GMB_CMD_CHECK_RUN}
}


# gmb_cmd_wipe_run
#
gmb_cmd_wipe_run()
{
    gmb_info "****  WIPE TARGET: ${GMB_TARGET_NAME}  ****"

    gmb_info "  RM '${GMB_TARGET_PREFIX}'"
    rm -rf "${GMB_TARGET_PREFIX}"
    
    local stamps=$(find "${GMB_STAMPS_DIR}/builds/${GMB_TARGET_NAME}" -name '*.install.stamp-h')
    gmb_info "  RM" ${stamps:-(no stamps)}
    [ x"${stamps}" = x ] || rm ${stamps}

    GMB_TARGET_COMMAND_BUILDS=$(echo "${GMB_TARGET_COMMAND_BUILDS}" | gmb_strstrip)
    if [ x"${GMB_TARGET_COMMAND_BUILDS}" = x ]; then
        gmb_info "NOTE: no object selected, select all"
        GMB_TARGET_COMMAND_BUILDS=$(gmb_list_target_builds "${GMB_TARGET_NAME}")
    fi

    for build in ${GMB_TARGET_COMMAND_BUILDS}; do
        gmb_info "****  WIPE BUILD: ${build}  ****"
        gmb_env_push
        gmb_config_build "${build}"

        gmb_info "  RM '${GMB_BUILD_BUILD_DIR}'"
        rm -rf "${GMB_BUILD_BUILD_DIR}"
        
        local stamps=$(find "${GMB_STAMPS_DIR}/builds" -name "${GMB_BUILD_NAME}.*.stamp-h")
        gmb_info "  RM" ${stamps:-(no stamps)}
        [ x"${stamps}" = x ] || rm ${stamps}
        
        gmb_env_pop
    done
}
